#include "channel.h"

#include <QDomDocument>
#include <QSettings>
#include <QFont>
#include <QDebug>
#include <QRegExp>

#define MAX_PROGRAMS 100

Program::~Program()
{
    foreach (Stream* s, streams)
        delete s;
}

qint64 Program::diff(const Program& other) const
{
    return qint64(other.pubDate.toTime_t()) - qint64(pubDate.toTime_t());
}

bool Program::pubDateOrder(const Program* lhs, const Program* rhs)
{
    return lhs->pubDate < rhs->pubDate;
}

Channel::~Channel()
{
    foreach (Program* p, programs)
        delete p;
}

void Channel::update(QXmlQuery& query)
{
    // mark all existing programs as "not seen yet"
    foreach (Program* p, programs)
        p->seen = false;
    
    QDateTime oldestRetrieved = QDateTime::currentDateTime();
    for (QUrl url = link; !url.isEmpty() && programs.count() < MAX_PROGRAMS; ) {
        // query all channel's programs
        if (!query.setFocus(url))
            break;
        query.setQuery("declare namespace atom=\"http://www.w3.org/2005/Atom\";\n"
                       "let $prev := //atom:link[@rel=\"prev-archive\"]/@href\n"
                       "return <p prev=\"{$prev}\">{//item}</p>");
        QString programsXml;
        query.evaluateTo(&programsXml);
        //qDebug() << programsXml;
    
        // start queries for all channels' programs
        QDomDocument programsDoc;
        programsDoc.setContent(programsXml);
        bool needPrev = false;
        for (QDomElement node = programsDoc.documentElement().firstChildElement("item");
             !node.isNull();
             node = node.nextSiblingElement("item"))
        {
            Program* p = new Program;
            p->seen = true;
            p->title = node.firstChildElement("title").text();
            p->description = node.firstChildElement("description").text();
            p->link = url.resolved(node.firstChildElement("link").text());
            p->guid = node.firstChildElement("guid").text();
            const QString dateFormat = "ddd, dd MMM yyyy hh:mm:ss";
            const QString date = node.firstChildElement("pubDate").text();
            QRegExp tzOffset("\\s([+-]\\d\\d)(\\d\\d)$");
            int tzIndex = tzOffset.indexIn(date);
            if (tzIndex > 0) {
                // parse date/time part
                p->pubDate = QDateTime::fromString(date.left(tzIndex), dateFormat);
                // then adjust by timezone offset
                int offset = date.mid(tzOffset.pos(1), 3).toInt() * 60
                           + date.mid(tzOffset.pos(2), 2).toInt();
                p->pubDate.setUtcOffset(offset * 60);
            }
            else
                p->pubDate = QDateTime::fromString(date, dateFormat); // use whole string
            oldestRetrieved = qMin(oldestRetrieved, p->pubDate);

            QDomElement mediaGroup = node.firstChildElement("media:group");
            p->thumbnail = mediaGroup.firstChildElement("media:thumbnail").attribute("url");
            p->duration = 0;
            for (QDomElement stream = mediaGroup.firstChildElement("media:content");
                 !stream.isNull();
                 stream = stream.nextSiblingElement("media:content"))
            {
                Stream* s = new Stream;
                s->url = url.resolved(stream.attribute("url"));
                s->mimeType = stream.attribute("type");
                s->size = stream.attribute("fileSize").toLongLong();
                s->bitrate = stream.attribute("bitrate").toInt();
                p->duration = qMax(p->duration, stream.attribute("duration").toInt());
                s->isDefault = QVariant(stream.attribute("isDefault")).toBool();
                p->streams.append(s);
            }

            // update or add this program
            bool found = false;
            QMutableListIterator<Program*> i(programs);
            while (!found && i.hasNext()) {
                Program*& program = i.next();
                if (program->guid == p->guid) {
                    delete program;
                    program = p;
                    found = true;
                }
            }
            if (!found) {
                programs.append(p);
                // found new program, check even older listings for more
                needPrev = true;
            }
        }
        
        // check if we really should request previous items
        if (needPrev)
            url = url.resolved(programsDoc.documentElement().attribute("prev"));
        else
            url = QUrl();
    }
        
    // Delete unseen programs, but only those that are in date range of elements retrieved
    // above. This allows keeping the old program items in history.
    QMutableListIterator<Program*> i(programs);
    while (i.hasNext()) {
        Program*& p = i.next();
        if (!p->seen && p->pubDate >= oldestRetrieved) {
            delete p;
            i.remove();
        }
    }

    // sort program list chronologically
    qSort(programs.begin(), programs.end(), Program::pubDateOrder);
}

int Channel::columnCount(const QModelIndex& /*parent*/) const
{
    return 2;
}

int Channel::rowCount(const QModelIndex& /*parent*/) const
{
    return programs.count();
}

QVariant Channel::data(const QModelIndex& index, int role) const
{
    if (!index.isValid())
        return QVariant();
    
    const Program* p = programs[index.row()];
    
    switch (role) {
        case Qt::DisplayRole:
            if (index.column() == 0)
                return p->pubDate.toLocalTime().toString("hh:mm");
            else if (index.column() == 1)
                return p->title;
            break;
            
        case Qt::FontRole:
            QFont font;
            if (index.column() == 0)
                font.setBold(true);
                return font;
            break;
    }
    return QVariant();
}
